type loc = int;;

type regval = Value of int;;
    
type regbank = Regbank of (reg -> regval);;

(* eccezioni per Environment *)
exception TypeMismatch of string;;

(* Registers Bank *)
let emptyregbank = fun () -> Regbank (fun x ->Value 0);;

let readreg (Regbank rho) x = match rho x with    
  | Value d -> d
;;

let writereg (Regbank bank) reg value = match reg with
	| "$0" -> (Regbank bank)
	| "$1"| "$2"	| "$3" 	| "$4" 	| "$5" 	| "$6" 	| "$7" 	| "$8" 	
	| "$9" 	| "$10" | "$11" | "$12" | "$13" | "$14" 	| "$15" 
	| "$16" | "$31" | "$fp" | "$sp" -> (Regbank (fun reg' -> if reg' = reg then value else bank reg'))
	(* devono essere 32 o 64 *)
	;;	
	 	
(* dichiarazioni di tipo per Store *)
type mval = int;;
 
type asm_store = Mem of (loc -> mval) * int;;

(* eccezioni per Store *)
exception AddressOutOfBounds of loc;;

(* Store *)
let emptystore = fun () -> Mem ((fun x -> 0), 0x1FFFF);;

let applystore (Mem (sigma,dim)) l = 
    if 0 <= l && l < dim then sigma l
    else raise (AddressOutOfBounds l)
;;

let update (Mem (sigma,dim)) l m = 
    if 0 <= l && l < dim then Mem ((fun l' -> if l'=l then m else sigma l'), dim)
    else raise (AddressOutOfBounds l);;

(* eccezioni per Expressions *)
exception TypeMismatch of string;;
exception DEBUG of string;;
  
(* Tipi per programmi *)
exception InstructionNotFound of string;;

let rec fetch program n = match (program,n) with
	| ( hd :: tail ,0) -> program
	| ( hd :: tail, m) ->  fetch tail (m-1)
	| ( [], _ ) -> raise (InstructionNotFound("Do not exist any instruction at this line"))
	;;

let lookup prog l =
	let rec do_lookup prog l n = match prog with
		| [] -> raise (InstructionNotFound("Do not exist any instruction at this label"))
		| hd :: tail -> if (fst hd) = l then n else do_lookup tail l (n+1)	
in do_lookup prog l 0;; 



(*Valutazione dei comandi*)
let rec sem_asm_com (prog,is,bank,store,n) = match is with
	| [] -> (bank,store)
	| hd :: tail -> match (snd hd) with
		| Halt -> (bank,store)
		| Nop -> sem_asm_com(prog,tail,bank,store,n+1)
		| Add (rdest,r1,r2) ->sem_asm_com(prog,tail, 
													writereg bank rdest ( Value( (readreg bank r1) + (readreg bank r2))) ,store,n+1)
		| AddI (rdest,i,r2) ->sem_asm_com(prog,tail, 
													writereg bank rdest ( Value( (i) + (readreg bank r2))) ,store,n+1)
		| Sub (rdest,r1,r2) ->sem_asm_com(prog,tail, 
													writereg bank rdest ( Value( (readreg bank r1) - (readreg bank r2))) ,store,n+1)
		| Mul (rdest,r1,r2) ->sem_asm_com(prog,tail, 
													writereg bank rdest ( Value( (readreg bank r1) * (readreg bank r2))) ,store,n+1)
    | Store(base, offset, value) -> sem_asm_com(prog,tail,bank, 
					(update store ((readreg bank base) + (readreg bank offset)) (readreg bank value)), n+1)
		| Load(rdest, base, offset) ->  sem_asm_com(prog,tail,
					(writereg bank rdest (Value (applystore store ((readreg bank base) + (readreg bank offset))))),
					store,n+1)
		| Jump(lname) -> sem_asm_com(prog,(fetch prog (lookup prog lname)),bank,store,(lookup prog lname))
		| Beq(r1,r2,lname) -> sem_asm_com(prog,
			(if (readreg bank r1) = (readreg bank r2) then ((fetch prog (lookup prog lname))) else tail),
			bank,store, 
			(if (readreg bank r1) = (readreg bank r2) then (lookup prog lname) else n+1)     )	
		| Bne(r1,r2,lname) -> sem_asm_com(prog,
			(if (readreg bank r1) <> (readreg bank r2) then ((fetch prog (lookup prog lname))) else tail),
			bank,store, 
			(if (readreg bank r1) <> (readreg bank r2) then (lookup prog lname) else n+1)     )	
		| Slt(rdest,r1,r2) -> sem_asm_com(prog,tail, 
			(if (readreg bank r1) < (readreg bank r2) then (writereg bank rdest (Value 1))
																								else (writereg bank rdest (Value 0))), store, n+1)		  
		| JR ->   let lname = string_of_int(readreg bank "$31") in 
			sem_asm_com(prog,(fetch prog (lookup prog lname)),bank,store,(lookup prog lname))
		(*| Jal(label,reg,reg,int) -> sem_asm_com(prog,(fetch prog (lookup prog lname)),(writereg bank "$31" (Value (n+1))),store,lookup(prog,label))*)
;;

let rec range a b = 
  if a>b then []
  else a::(range (a+1) b);;

let asm_dump (program) = match program with
	| ASM_Program prog ->( 
  let (bank',store') =  
  sem_asm_com (prog,prog,emptyregbank(),emptystore(),0)
  in ( List.map (applystore store') (range 0 100) ))	
;;

